package com.example.gobang_system.websockethandler;

import com.example.gobang_system.common.errorcode.HandlerErrorCodeConstants;
import com.example.gobang_system.common.pojo.CommonResult;
import com.example.gobang_system.common.pojo.UserInfo;
import com.example.gobang_system.common.util.JacksonUtil;
import com.example.gobang_system.service.GameService;
import com.example.gobang_system.service.manager.OnlineUserManager;
import com.example.gobang_system.common.pojo.Room;
import com.example.gobang_system.service.manager.RoomManager;
import com.example.gobang_system.websockethandler.param.PutChessParam;
import com.example.gobang_system.websockethandler.result.GameReadyResult;
import com.example.gobang_system.websockethandler.result.PutChessResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;

import static com.example.gobang_system.service.UserService.USER_INFO;

@Component
@Slf4j
public class GameHandler extends TextWebSocketHandler {
    @Autowired
    private OnlineUserManager onlineUserManager;
    @Autowired
    private RoomManager roomManager;
    @Autowired
    private GameService gameService;

    /**
     * 连接建立(游戏准备)
     * @param session
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 从 session 中获取用户信息
        UserInfo userInfo = (UserInfo) session.getAttributes().get(USER_INFO);
        // 用户是否登录
        if (null == userInfo) {
            session.sendMessage(new TextMessage(JacksonUtil.writeValueAsString(
                    CommonResult.noLogin())));
            return;
        }
        // 判断用户是否处于在线状态
        if (null != onlineUserManager.getFromHall(userInfo.getUserId())) {
            session.sendMessage(new TextMessage(JacksonUtil.writeValueAsString(
                    CommonResult.repeatConnection())));
            return;
        }
        // 获取游戏房间, 判断当前玩家是否在游戏房间中
        Room room = roomManager.getRoomByUserId(userInfo.getUserId());
        if (null == room) {
            session.sendMessage(new TextMessage(JacksonUtil.writeValueAsString(
                    CommonResult.fail(HandlerErrorCodeConstants.GET_GAME_ROOM_ERROR))));
            return;
        }
        // 将用户设置为在线状态
        onlineUserManager.enterGameRoom(userInfo.getUserId(), session);
        // 将玩家加入到游戏房间(加锁)
        synchronized (room) {
            // 第一个玩家是否加入游戏房间
            if (null == room.getUser1()) {
                room.setUser1(userInfo);
                // 默认玩家1 作为先手
                room.setWhiteUserId(userInfo.getUserId());
                log.info("房间 {} 玩家1 {} 已就绪", room.getRoomId(), userInfo.getUserName());
                return;
            }
            // 第二个玩家是否加入游戏房间
            if (null == room.getUser2()) {
                room.setUser2(userInfo);
                log.info("房间 {} 玩家2 {} 已就绪", room.getRoomId(), userInfo.getUserName());
                // 通知玩家1 和 玩家2 游戏已就绪
                notifyGameReady(room, room.getUser1(), room.getUser2());
                notifyGameReady(room, room.getUser2(), room.getUser1());
                return;
            }
        }
        // 还有玩家尝试进入同一房间，提示房间已满
        // 理论上不会出现该情况，为了让程序更加的健壮, 还是进行判定和提示
        session.sendMessage(new TextMessage(JacksonUtil.writeValueAsString(
                CommonResult.fail(HandlerErrorCodeConstants.GAME_ROOM_IS_FULL))));
        // 日志打印
        log.info("玩家 {} 进入游戏房间", userInfo.getUserName());
    }

    /**
     * 通知玩家游戏已准备就绪
     * @param room 游戏房间
     * @param thisUser 玩家
     * @param thatUser 对手
     */
    private void notifyGameReady(Room room, UserInfo thisUser, UserInfo thatUser) {
        try {
            // 构造游戏就绪响应
            GameReadyResult gameReadyResult = new GameReadyResult();
            gameReadyResult.setRoomId(room.getRoomId());
            gameReadyResult.setThisUserId(thisUser.getUserId());
            gameReadyResult.setThatUserId(thatUser.getUserId());
            gameReadyResult.setWhiteUserId(room.getWhiteUserId());
            // 发送游戏就绪响应
            WebSocketSession session = onlineUserManager.getFromRoom(thisUser.getUserId());
            session.sendMessage(new TextMessage(JacksonUtil.writeValueAsString(
                    CommonResult.success(gameReadyResult))));
        } catch (Exception e) {
            log.warn("notifyGameReady 通知玩家游戏准备就绪异常 e: ", e);
        }
    }

    /**
     * 处理落子请求
     * @param session
     * @param message
     * @throws Exception
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        UserInfo userInfo = (UserInfo) session.getAttributes().get(USER_INFO);
        // 用户是否登录
        if (null == userInfo) {
            session.sendMessage(new TextMessage(JacksonUtil.writeValueAsString(
                    CommonResult.noLogin())));
            return;
        }
        // 获取游戏房间
        Room room = roomManager.getRoomByUserId(userInfo.getUserId());
        if (null == room) {
            session.sendMessage(new TextMessage(JacksonUtil.writeValueAsString(
                    CommonResult.fail(HandlerErrorCodeConstants.GET_GAME_ROOM_ERROR))));
        }
        // 获取用户端发送的数据
        String payload = message.getPayload();
        // 将 JSON 字符串转化为 java 对象
        PutChessParam putChessParam = JacksonUtil.readValue(payload, PutChessParam.class);
        // 根据请求落子
        gameService.putChess(putChessParam, userInfo);
    }

    /**
     * 处理异常情况
     * @param session
     * @param exception
     * @throws Exception
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        // 打印错误信息
        log.error("游戏过程中出现异常: ", exception);
        logoutFromRoom(session);
    }

    /**
     * 退出游戏房间
     * @param session
     */
    private void logoutFromRoom(WebSocketSession session) {
        // 获取用户信息
        UserInfo userInfo = (UserInfo) session.getAttributes().get(USER_INFO);
        // 判断用户是否登录
        if (null == userInfo) {
            return;
        }
        // 获取游戏房间中存储的玩家信息
        WebSocketSession onlineSession = onlineUserManager.getFromRoom(userInfo.getUserId());
        // 多开情况，直接退出
        if (session != onlineSession) {
            return;
        }
        // 玩家下线
        onlineUserManager.exitGameRoom(userInfo.getUserId());
        // 当前玩家下线，对手胜利
        notifyThatUserWin(userInfo);
        log.info("玩家 {} 从游戏房间退出", userInfo.getUserName());
    }

    /**
     * 通知对手胜利
     * @param userInfo
     */
    private void notifyThatUserWin(UserInfo userInfo) {
        try {
            // 查找玩家下线所在房间
            Room room = roomManager.getRoomByUserId(userInfo.getUserId());
            // 判断游戏房间是否已经销毁
            if (null == room) {
                return;
            }
            // 判断对手是否已经下线
            UserInfo thatUser = (room.getUser1() == userInfo) ?
                    room.getUser2() : room.getUser1();
            if (null == thatUser) {
                return;
            }
            // 对手会话是否存在
            WebSocketSession thatUserSession = onlineUserManager.getFromRoom(thatUser.getUserId());
            if (null == thatUserSession) {
                return;
            }
            // 返回响应
            PutChessResult result = new PutChessResult();
            result.setUserId(thatUser.getUserId());
            result.setRow(-1);
            result.setCol(-1);
            result.setWinner(thatUser.getUserId());
            thatUserSession.sendMessage(new TextMessage(JacksonUtil.writeValueAsString(
                    CommonResult.success(result))));
            log.info("房间{} 玩家 {} 胜利！", room.getRoomId(), thatUser.getUserId());
            // 销毁游戏房间
            roomManager.remove(
                    room.getRoomId(), room.getUser1().getUserId(), room.getUser2().getUserId());
            // 更新玩家分数
            gameService.updateScore(thatUser.getUserId(), userInfo.getUserId());
        } catch (IOException e) {
            log.warn("通知玩家下线异常 e: ", e);
        } catch (Exception e) {
            log.warn("玩家下线异常 e: ", e);
        }
    }

    /**
     * 连接关闭
     * @param session
     * @param status
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        log.info("游戏连接断开, code: {}, reason: {}",
                status.getCode(), status.getReason());
        // 玩家下线
        logoutFromRoom(session);
    }
}
